﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Compilex.BaseLibrary;
using Compilex.Syntatic.Declarations;
using Compilex.Syntatic.Descriptors;

namespace Compilex.Syntatic.Declarations
{
    public class FunctionDeclaration
    {
        public SyntaticContext Context { get; set; }
        public string Identifier { get; set; }
        public FunctionDescriptor Descriptor { get; set; }

        public FunctionDeclaration(SyntaticContext context)
        {
            this.Context = context;
            this.Descriptor = new FunctionDescriptor();
            this.Descriptor.IsVoid = true;
        }

        public void Start()
        {
            this.Descriptor = new FunctionDescriptor();
            this.Descriptor.IsVoid = true;
        }

        public void AddName()
        {
            this.Identifier = Context.CurrentToken;
            SymbolTableHelper.AssertFunctionIdentifierUndeclared(Identifier, Context);
            Context.ScopeManager.CurrentScope.SymbolTable.Add(
                this.Identifier, new Symbol(this.Identifier, SymbolClass.Funcex,
                    Descriptor));
           
        }

        public void AddParameter()
        {
            VariableDeclaration paramDecl = new VariableDeclaration(Context);
            paramDecl.StartFinishVariable();

            if (paramDecl.Descriptor.IsArray)
                throw new SyntaticException("Passagem de vetores para funções não é suportada.");

            paramDecl.Descriptor.IsParam = true;
            if(Descriptor.Params.Count > 0)
            {
                VariableDescriptor last = Descriptor.Params.ToArray().Last();
                paramDecl.Descriptor.StackOffset = last.StackOffset + last.SizeBytes;
            }
            else
            {
                paramDecl.Descriptor.StackOffset = 2 + Descriptor.ReturnAddressSize;
                // ainda nao sei o retorno para somar no offset, faço isso depois
            }
            Descriptor.Params.Add(paramDecl.Descriptor);            
        }

        public void AddArrayParamter()
        {
            VariableDeclaration paramDecl = new VariableDeclaration(Context);
            paramDecl.StartFinishArray();
            Descriptor.Params.Add(paramDecl.Descriptor);  
        }

        public void AddReturnType()
        {
           Descriptor.ReturnType = SymbolTableHelper.SymbolTypeFromString(Context.CurrentToken,
               Context);
           Descriptor.IsVoid = false;
           int paramStackOffset = 2 + Descriptor.ReturnAddressSize + Descriptor.ReturnType.SizeBytes;
           foreach (VariableDescriptor param in Descriptor.Params.ToArray().Reverse())
           {
               param.StackOffset = paramStackOffset;
               paramStackOffset += param.SizeBytes;
           }

        }

        public void StartFinishLocalVariable()
        {
            VariableDeclaration localVar = new VariableDeclaration(Context);
            localVar.StartFinishVariable();
            AddLocalVariable(localVar);
        }

        private void AddLocalVariable(VariableDeclaration localVar)
        {
            if (Descriptor.LocalVariables.Count > 0)
            {
                VariableDescriptor lastVar = (VariableDescriptor)
                    Descriptor.LocalVariables.ToArray().Last().Descriptor;
                localVar.Descriptor.StackOffset = lastVar.StackOffset + localVar.Descriptor.SizeBytes;
            }
            else
            {
                localVar.Descriptor.StackOffset = localVar.Descriptor.SizeBytes;
            }
            Descriptor.LocalVariables.Add(localVar.Symbol);
        }

        public void StartFinishLocalArray()
        {
            VariableDeclaration localVar = new VariableDeclaration(Context);
            localVar.StartFinishArray();
            AddLocalVariable(localVar);
        }

        public void ReturnFunctionBExpression()
        {
            Context.ExpressionStack.EndExpression();
            if (Descriptor.IsVoid)
            {
                throw new SyntaticException("Uma função void não pode retornar uma expressão.");
            }
            if (Context.ExpressionStack.JustPopped.Type != Descriptor.ReturnType)
            {
                throw new SyntaticException("Tipo do retorno não é o mesmo do tipo da função.");
            }
            int stackSize = 0;
            if (!Descriptor.IsVoid)
            {
                stackSize += Descriptor.ReturnType.SizeBytes;
            }
            stackSize += Descriptor.ReturnAddressSize + 2 + Descriptor.LocalVariablesStackSizeBytes + Descriptor.ParamsStackSize;

            Context.CommandBlockStack.Current.CodeGenerator.PushReturn(Descriptor, Identifier);
            Context.CommandBlockStack.Current.CodeGenerator.JumpToEpilogue(Identifier);
            //Context.CommandBlockStack.Current.CodeGenerator.Epilogue(stackSize, Descriptor.LocalVariablesStackSizeBytes);
        }

        public void ReturnVoid()
        {
            Context.CommandBlockStack.Current.CodeGenerator.JumpToEpilogue(Identifier);
        }

        public void EndFunction()
        {
            Context.CommandBlockStack.Current.CodeGenerator.EndFunction(Descriptor, Identifier);
            Context.ScopeManager.CloseScope();
            Context.CommandBlockStack.EndCommandBlock();
        }
    }
}
